Skip to content

feat(mcp): OAuth 2.0 (authorization-code + PKCE) for http/sse servers#122

Merged
oratis merged 1 commit into
mainfrom
feat/mcp-oauth
May 31, 2026
Merged

feat(mcp): OAuth 2.0 (authorization-code + PKCE) for http/sse servers#122
oratis merged 1 commit into
mainfrom
feat/mcp-oauth

Conversation

@oratis

@oratis oratis commented May 31, 2026

Copy link
Copy Markdown
Owner

Summary

Adds the last MCP §3.3 capability: connecting to MCP servers that require OAuth (Linear, GitHub-hosted MCP, etc.). The SDK drives the protocol; DeepCode supplies an OAuthClientProvider.

Opt-in per server:

{ "mcpServers": { "linear": { "url": "https://mcp.linear.app/sse", "oauth": true, "oauthScopes": ["read"] } } }

On first connect it opens the browser; tokens persist under ~/.deepcode/mcp-auth/<server>.json and auto-refresh thereafter.

Changes (packages/core/src/mcp/oauth.ts)

  • McpAuthStore — file-backed persistence of tokens / dynamic client registration / PKCE verifier, with scoped clear() (mirrors invalidateCredentials).
  • startLoopbackReceiver — a one-shot 127.0.0.1:<random>/callback server that captures ?code=/state from the redirect, validates state, and surfaces ?error=.
  • DeepCodeOAuthProvider implements OAuthClientProvider — PKCE client metadata (token_endpoint_auth_method: none, authorization_code + refresh_token grants), store-backed tokens/clientInformation/codeVerifier, opens the system browser (overridable), invalidateCredentials(scope).

Wiring (client.ts): connectMcpServer creates the provider (http/sse only) and passes it as the transport authProvider. On the first UnauthorizedError, it awaits the loopback code, calls transport.finishAuth(code), and reconnects. Added oauth / oauthScopes to McpServerConfig.

Tests (+9, oauth.test.ts)

  • McpAuthStore: empty read, patch+merge persistence, clear by scope vs all.
  • Loopback: captures the code (real fetchwaitForCode), rejects on ?error=, rejects on state mismatch.
  • Provider: PKCE client metadata points at the loopback redirect + joins scopes; token+verifier persist across instances; codeVerifier() throws when none saved; redirectToAuthorization opens the URL.

Core suite 597 green; full repo green via pre-commit.

Verification boundary

The persistence, loopback, metadata, and connect/retry wiring are unit-tested and follow the SDK's documented authProvider + finishAuth contract. The live token exchange is performed by the SDK (which has its own coverage); a full end-to-end run against a real OAuth provider is manual (no OAuth server in CI).

🤖 Generated with Claude Code

Adds the last MCP §3.3 capability: connect to MCP servers that require OAuth.
The SDK drives the protocol; we supply an OAuthClientProvider (mcp/oauth.ts):

- McpAuthStore — persists tokens / dynamic client registration / PKCE verifier
  to ~/.deepcode/mcp-auth/<server>.json (auto-refresh handled by the SDK).
- startLoopbackReceiver — one-shot 127.0.0.1 server that captures the
  ?code=/state from the browser redirect (validates state, surfaces errors).
- DeepCodeOAuthProvider — PKCE client metadata (token_endpoint_auth_method
  'none', authorization_code + refresh_token grants), store-backed persistence,
  opens the system browser, invalidateCredentials by scope.

Wiring (client.ts): opt-in via `mcpServers.<name>.oauth: true` (+ optional
`oauthScopes`). connectMcpServer passes the provider as the transport
authProvider; on the first `UnauthorizedError` it awaits the loopback code,
calls transport.finishAuth(code), and reconnects. stdio servers ignore it.

Tests (+9): McpAuthStore read/patch/clear-by-scope; loopback captures the code
/ rejects on error / state-mismatch; provider PKCE metadata, token+verifier
persistence across instances, browser-open + missing-verifier error. Core 597.

Note: the live token exchange is exercised by the SDK; end-to-end against a
real provider is manual (no OAuth server in CI).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@oratis oratis merged commit 9389df8 into main May 31, 2026
3 checks passed
@oratis oratis deleted the feat/mcp-oauth branch May 31, 2026 09:31
oratis added a commit that referenced this pull request May 31, 2026
OAuth 2.0 (authorization-code + PKCE) landed in #122 — the last 🔄 in the MCP
section is now ✅. With it the MCP §3.3 parity line is complete.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant